package com.mathlearning.auth.service;

import com.mathlearning.auth.common.ApiResponse;
import com.mathlearning.auth.dto.*;
import com.mathlearning.auth.entity.User;
import com.mathlearning.auth.entity.UserLoginLog;
import com.mathlearning.auth.repository.UserRepository;
import com.mathlearning.auth.repository.UserLoginLogRepository;
import com.mathlearning.auth.util.CaptchaUtil;
import com.mathlearning.auth.util.JwtUtil;
import com.mathlearning.auth.util.PhoneUtil;
import com.mathlearning.auth.util.WeChatUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.UUID;

@Service
public class AuthService {

    private static final Logger logger = LoggerFactory.getLogger(AuthService.class);

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private UserLoginLogRepository loginLogRepository;

    @Autowired
    private CaptchaUtil captchaUtil;

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Autowired
    private SmsService smsService;

    @Autowired
    private WeChatUtil weChatUtil;

    /**
     * 获取图片验证码
     */
    public ApiResponse<CaptchaResponse> getCaptcha(HttpServletRequest request) {
        String code = captchaUtil.generateCode();
        String token = UUID.randomUUID().toString().replace("-", "");
        String svg = captchaUtil.generateSvg(code);

        // 存储到Redis，5分钟过期
        String key = "captcha:" + token;
        redisTemplate.opsForValue().set(key, code, Duration.ofMinutes(5));

        // 记录IP
        String ip = getClientIp(request);
        String ipKey = "captcha_ip:" + token;
        redisTemplate.opsForValue().set(ipKey, ip, Duration.ofMinutes(5));

        return ApiResponse.success(new CaptchaResponse(token, svg));
    }

    /**
     * 发送短信验证码
     */
    public ApiResponse<?> sendSmsCode(SendSmsRequest request, String captchaToken, HttpServletRequest httpRequest) {
        String phone = request.getPhone();
        String type = request.getType();
        String captchaCode = request.getCaptchaCode();

        // 兼容：若 query 未带 captchaToken，而 body 带了 token，则回退使用 body 的
        if (captchaToken == null || captchaToken.isEmpty()) {
            captchaToken = request.getCaptchaToken();
        }

        // 统一验证码规范：去空格、小写
        if (captchaCode != null) {
            captchaCode = captchaCode.trim().toLowerCase();
        }

        // 1. 验证手机号格式
        if (!PhoneUtil.isValid(phone)) {
            throw new com.mathlearning.auth.common.BusinessException("手机号格式不正确");
        }

        // 2. 根据类型检查手机号状态
        boolean userExists = userRepository.existsByPhone(phone);
        if ("login".equals(type)) {
            // 登录验证码：必须已注册
            if (!userExists) {
                throw new com.mathlearning.auth.common.BusinessException("该手机号未注册，请先注册");
            }
        } else if ("register".equals(type)) {
            // 注册验证码：不能已注册
            if (userExists) {
                throw new com.mathlearning.auth.common.BusinessException("该手机号已注册，请直接登录");
            }
        }

        // 3. 验证图片验证码（需要token）- 仅校验，不消费
        if (captchaToken != null) {
            validateCaptchaByToken(captchaToken, captchaCode, false);
        }

        // 4. 检查发送频率（60秒内只能发1次）
        String coolingKey = "sms_cooling:" + phone;
        if (redisTemplate.hasKey(coolingKey)) {
            throw new com.mathlearning.auth.common.BusinessException("发送过于频繁，请稍后再试");
        }

        // 5. 检查1小时内最多5次
        String limitKey = "sms_limit:" + phone;
        String countStr = redisTemplate.opsForValue().get(limitKey);
        int count = countStr != null ? Integer.parseInt(countStr) : 0;
        if (count >= 5) {
            throw new com.mathlearning.auth.common.BusinessException("发送次数过多，请稍后再试");
        }

        // 6. 生成6位数字验证码
        String smsCode = String.valueOf((int) (Math.random() * 900000 + 100000));

        // 7. 调用短信服务（需要实现）
        try {
            smsService.sendSms(phone, smsCode);
        } catch (Exception e) {
            throw new com.mathlearning.auth.common.BusinessException("短信发送失败，请稍后重试");
        }

        // 8. 存储到Redis，5分钟过期
        String smsKey = "sms:" + phone + ":" + type;
        redisTemplate.opsForValue().set(smsKey, smsCode, Duration.ofMinutes(5));

        // 9. 设置冷却期60秒
        redisTemplate.opsForValue().set(coolingKey, "1", Duration.ofSeconds(60));

        // 10. 设置频率计数
        redisTemplate.opsForValue().set(limitKey, String.valueOf(count + 1), Duration.ofHours(1));

        return ApiResponse.success(200, "发送成功");
    }

    /**
     * 验证码登录
     */
    @Transactional
    public ApiResponse<LoginResponse> loginBySms(SmsLoginRequest request, HttpServletRequest httpRequest) {
        String phone = request.getPhone();
        String code = request.getCode();

        // 1. 验证验证码
        String smsKey = "sms:" + phone + ":login";
        String storedCode = redisTemplate.opsForValue().get(smsKey);
        if (storedCode == null || !storedCode.equals(code)) {
            throw new com.mathlearning.auth.common.BusinessException("验证码错误或已过期");
        }

        // 2. 查询用户（在发送验证码时已检查，这里一般不会出现用户不存在的情况）
        User user = userRepository.findByPhone(phone)
                .orElseThrow(() -> new com.mathlearning.auth.common.BusinessException("用户不存在，请先注册"));

        // 3. 删除已使用的验证码
        redisTemplate.delete(smsKey);

        // 4. 生成JWT token
        String token = jwtUtil.generateToken(user.getId(), user.getPhone());

        // 5. 更新用户登录信息
        String ip = getClientIp(httpRequest);
        user.setLastLoginTime(LocalDateTime.now());
        user.setLastLoginIp(ip);
        userRepository.save(user);

        // 6. 记录登录日志
        UserLoginLog log = new UserLoginLog();
        log.setUserId(user.getId());
        log.setLoginType("sms");
        log.setIp(ip);
        log.setUserAgent(httpRequest.getHeader("User-Agent"));
        loginLogRepository.save(log);

        return ApiResponse.success(new LoginResponse(token, new UserInfo(user)));
    }

    /**
     * 密码登录
     */
    @Transactional
    public ApiResponse<LoginResponse> loginByPassword(PasswordLoginRequest request, HttpServletRequest httpRequest) {
        String phone = request.getPhone();
        String password = request.getPassword();

        // 1. 查询用户
        User user = userRepository.findByPhone(phone)
                .orElseThrow(() -> new com.mathlearning.auth.common.BusinessException("手机号或密码错误"));

        // 2. 验证密码
        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw new com.mathlearning.auth.common.BusinessException("手机号或密码错误");
        }

        // 3. 生成JWT token
        String token = jwtUtil.generateToken(user.getId(), user.getPhone());

        // 4. 更新用户登录信息
        String ip = getClientIp(httpRequest);
        user.setLastLoginTime(LocalDateTime.now());
        user.setLastLoginIp(ip);
        userRepository.save(user);

        // 5. 记录登录日志
        UserLoginLog log = new UserLoginLog();
        log.setUserId(user.getId());
        log.setLoginType("password");
        log.setIp(ip);
        log.setUserAgent(httpRequest.getHeader("User-Agent"));
        loginLogRepository.save(log);

        return ApiResponse.success(new LoginResponse(token, new UserInfo(user)));
    }

    /**
     * 用户注册
     */
    @Transactional
    public ApiResponse<LoginResponse> register(RegisterRequest request, String captchaToken, HttpServletRequest httpRequest) {
        String phone = request.getPhone();
        String password = request.getPassword();
        String code = request.getSmsCode();
        String bodyToken = request.getCaptchaToken();
        if (captchaToken == null || captchaToken.isEmpty()) {
            captchaToken = bodyToken; // 兼容 body.token
        }
        String captchaCode = request.getCaptchaCode();
        if (captchaCode != null) {
            captchaCode = captchaCode.trim().toLowerCase();
        }

        // 1. 验证图片验证码（需要token）
        if (captchaToken != null) {
            validateCaptchaByToken(captchaToken, captchaCode, true); // 在注册处消费
        }

        // 2. 检查手机号是否已注册
        if (userRepository.existsByPhone(phone)) {
            throw new com.mathlearning.auth.common.BusinessException("手机号已注册");
        }

        // 3. 验证短信验证码
        String smsKey = "sms:" + phone + ":register";
        String storedCode = redisTemplate.opsForValue().get(smsKey);
        if (storedCode == null || !storedCode.equals(code)) {
            throw new com.mathlearning.auth.common.BusinessException("验证码错误或已过期");
        }

        // 4. 创建用户
        User user = new User();
        user.setPhone(phone);
        user.setPassword(passwordEncoder.encode(password));
        user.setNickname("用户" + phone);
        user.setStatus(1);
        user = userRepository.save(user);

        // 5. 删除已使用的验证码
        redisTemplate.delete(smsKey);

        // 6. 生成JWT token
        String token = jwtUtil.generateToken(user.getId(), user.getPhone());

        // 7. 更新用户登录信息
        String ip = getClientIp(httpRequest);
        user.setLastLoginTime(LocalDateTime.now());
        user.setLastLoginIp(ip);
        userRepository.save(user);

        // 8. 记录登录日志
        UserLoginLog log = new UserLoginLog();
        log.setUserId(user.getId());
        log.setLoginType("register");
        log.setIp(ip);
        log.setUserAgent(httpRequest.getHeader("User-Agent"));
        loginLogRepository.save(log);

        return ApiResponse.success(new LoginResponse(token, new UserInfo(user)));
    }

    /**
     * 检查手机号是否存在
     */
    public ApiResponse<CheckPhoneResponse> checkPhone(CheckPhoneRequest request) {
        boolean exists = userRepository.existsByPhone(request.getPhone());
        return ApiResponse.success(new CheckPhoneResponse(exists));
    }

    /**
     * 获取微信授权URL
     */
    public String getWxAuthUrl() {
        String state = UUID.randomUUID().toString();
        return weChatUtil.getAuthUrl(state);
    }

    /**
     * 微信登录
     */
    @Transactional
    public ApiResponse<LoginResponse> loginByWeChat(String code, HttpServletRequest httpRequest) {
        try {
            // 1. 通过code获取access_token
            Map<String, Object> tokenMap = weChatUtil.getAccessToken(code);
            
            if (tokenMap.containsKey("errcode")) {
                throw new com.mathlearning.auth.common.BusinessException("微信授权失败，请重试");
            }
            
            String accessToken = (String) tokenMap.get("access_token");
            String openid = (String) tokenMap.get("openid");
            String unionid = (String) tokenMap.get("unionid");
            
            // 2. 获取用户信息
            Map<String, Object> userInfo = weChatUtil.getUserInfo(accessToken, openid);
            String nickname = (String) userInfo.get("nickname");
            String headimgurl = (String) userInfo.get("headimgurl");
            
            // 3. 查找或创建用户
            User user = userRepository.findByOpenid(openid).orElse(null);
            
            if (user == null) {
                // 未注册，自动注册
                user = new User();
                user.setOpenid(openid);
                user.setUnionid(unionid);
                user.setNickname(nickname != null ? nickname : "微信用户");
                user.setAvatar(headimgurl);
                user.setPhone(""); // 微信登录不需要手机号
                user.setPassword(passwordEncoder.encode("wx_" + System.currentTimeMillis())); // 随机密码
                user.setStatus(1);
                user = userRepository.save(user);
            } else {
                // 已注册，更新微信信息
                if (nickname != null) {
                    user.setNickname(nickname);
                }
                if (headimgurl != null) {
                    user.setAvatar(headimgurl);
                }
                if (unionid != null) {
                    user.setUnionid(unionid);
                }
                user = userRepository.save(user);
            }
            
            // 4. 生成JWT token
            String token = jwtUtil.generateToken(user.getId(), user.getPhone());
            
            // 5. 更新登录信息
            String ip = getClientIp(httpRequest);
            user.setLastLoginTime(LocalDateTime.now());
            user.setLastLoginIp(ip);
            userRepository.save(user);
            
            // 6. 记录登录日志
            UserLoginLog log = new UserLoginLog();
            log.setUserId(user.getId());
            log.setLoginType("wechat");
            log.setIp(ip);
            log.setUserAgent(httpRequest.getHeader("User-Agent"));
            loginLogRepository.save(log);
            
            return ApiResponse.success(new LoginResponse(token, new UserInfo(user)));
            
        } catch (Exception e) {
            logger.error("微信登录失败", e);
            throw new com.mathlearning.auth.common.BusinessException("微信登录失败，请重试");
        }
    }

    /**
     * 根据token验证图片验证码
     */
    private void validateCaptchaByToken(String captchaToken, String captchaCode, boolean consume) {
        String key = "captcha:" + captchaToken;
        String storedCode = redisTemplate.opsForValue().get(key);

        // 统一比较：两边都用小写
        if (storedCode == null || captchaCode == null || !storedCode.toLowerCase().equals(captchaCode)) {
            throw new com.mathlearning.auth.common.BusinessException("图片验证码错误或已过期");
        }

        // 仅在需要时消费
        if (consume) {
            redisTemplate.delete(key);
        }
    }

    /**
     * 获取客户端IP
     */
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}



